package cn.fntop.service;

import cn.fntop.config.CustomJacksonConverterFactory;
import cn.fntop.config.OpenApiProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import io.reactivex.Single;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import okhttp3.ConnectionPool;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import retrofit2.HttpException;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;

import java.net.Proxy;
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author fn
 * @description 初始化实例和上下文
 * @date 2023/8/14 8:51
 */
public abstract class OpenApiServiceAbstract implements OpenApiServiceSubject {
    /**
     * BASE_URL
     */
    private static final String BASE_URL = "http://127.0.0.1:8080/";
    /**
     * http超时时间
     */
    private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10);
    /**
     * 执行器
     */
    @Getter
    @Setter
    private static ExecutorService executorService;
    /**
     * 默认执行器超时shutdown时间/单位毫秒
     */
    @Getter
    @Setter
    private static long defaultExecutorTimeout = 500;

    /**
     * 默认结果获取超时时间/单位秒
     */
    @Getter
    @Setter
    private static long responseTimeout = 30;

    /**
     * 接口api超类
     */
    @Getter
    @Setter
    protected static OpenApi api;

    /**
     * 初始化实例并存入上下文
     * 默认方法
     *
     * @param properties
     * @param cls
     * @param subject
     * @return
     */
    @SneakyThrows
    public static OpenApiServiceSubject getInstance(OpenApiProperties properties, Class<? extends OpenApi> cls, Class<? extends OpenApiServiceSubject> subject) {
        return custom(properties, cls, subject, null, null, null);
    }

    /**
     * 自定义实例方法，上下文：{@link OpenApiContext}
     *
     * @param properties  @see {@link OpenApiProperties}
     * @param cls         implements {@link OpenApi}
     * @param subject     extends {@link OpenApiService}
     * @param timeout
     * @param interceptor implements {@link Interceptor}
     * @param proxy       new {@link Proxy}(Proxy.Type.HTTP, new InetSocketAddress(config.getProxyDomain(), config.getProxyPort()))
     * @param mapper      定义对象序列化策略
     * @return
     */
    @SneakyThrows
    public static OpenApiServiceSubject custom(OpenApiProperties properties, Class<? extends OpenApi> cls, Class<? extends OpenApiServiceSubject> subject, Duration timeout, Interceptor interceptor, Proxy proxy, ObjectMapper mapper) {
        if (OpenApiContext.get(subject) != null) {
            return OpenApiContext.get(subject);
        }
        if (mapper == null) {
            mapper = defaultObjectMapper();
        }
        OkHttpClient client = defaultClient(timeout, interceptor, proxy).newBuilder().build();
        Retrofit retrofit1 = defaultRetrofit(client, mapper, properties.getOpenGateway());
        api = retrofit1.create(cls);
        OpenApiServiceSubject instance = subject.newInstance();
        OpenApiContext.set(subject, instance);
        return instance;
    }

    /**
     * 支持序列化策略
     *
     * @param properties
     * @param cls
     * @param subject
     * @param mapper
     * @return
     */
    @SneakyThrows
    public static OpenApiServiceSubject custom(OpenApiProperties properties, Class<? extends OpenApi> cls, Class<? extends OpenApiServiceSubject> subject, ObjectMapper mapper) {
        return custom(properties, cls, subject, null, null, null,  mapper);
    }

    /**
     * 支持拦截器
     *
     * @param properties
     * @param cls
     * @param subject
     * @param interceptor
     * @return
     */
    @SneakyThrows
    public static OpenApiServiceSubject custom(OpenApiProperties properties, Class<? extends OpenApi> cls, Class<? extends OpenApiServiceSubject> subject, Interceptor interceptor) {
        return custom(properties, cls, subject, null, interceptor, null);
    }

    /**
     * 支持客户端连接超时设置
     *
     * @param properties
     * @param cls
     * @param subject
     * @param timeout
     * @return
     */
    @SneakyThrows
    public static OpenApiServiceSubject custom(OpenApiProperties properties, Class<? extends OpenApi> cls, Class<? extends OpenApiServiceSubject> subject, Duration timeout) {
        return custom(properties, cls, subject, timeout, null, null);
    }

    /**
     * 支持代理
     *
     * @param properties
     * @param cls
     * @param subject
     * @param proxy
     * @return
     */
    @SneakyThrows
    public static OpenApiServiceSubject custom(OpenApiProperties properties, Class<? extends OpenApi> cls, Class<? extends OpenApiServiceSubject> subject, Duration timeout, Interceptor interceptor, Proxy proxy) {
        return custom(properties, cls, subject, timeout, interceptor, proxy, null);
    }

    @SneakyThrows
    protected static <T> T execute(Single<T> apiCall) {
        if (executorService != null) {
            //多线程执行，暂定超时时间为30秒
            T response = executorService.submit(() -> exe(apiCall)).get(responseTimeout, TimeUnit.MILLISECONDS);
            try {
                //先停止接收任务，然后再等待一定的时间（默认500毫秒）让所有的任务都执行完毕，如果超过了给定的时间，则立刻结束任务。
                executorService.shutdown();
                if (!executorService.awaitTermination(defaultExecutorTimeout, TimeUnit.MILLISECONDS)) {
                    executorService.shutdownNow();
                }
            } catch (InterruptedException e) {
                executorService.shutdownNow();
            }
            return response;
        } else {
            return exe(apiCall);
        }
    }

    private static <T> T exe(Single<T> apiCall) {
        try {
            return apiCall.blockingGet();
        } catch (HttpException e) {
            throw new RuntimeException("接口调用失败" + e.getMessage());
        } finally {
            if (executorService != null) {
                executorService.shutdown();
            }
        }
    }

    private static ObjectMapper defaultObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
        return mapper;
    }

    private static OkHttpClient defaultClient(Duration timeout) {
        return defaultClient(timeout, null, null);
    }

    /**
     * @param timeout     超时
     * @param interceptor 自定义拦截器
     * @param proxy       自定义代理 new {@link Proxy}({@link Proxy.Type}, new InetSocketAddress("你的代理ip", "代理端口"))
     * @return
     */
    private static OkHttpClient defaultClient(Duration timeout, Interceptor interceptor, Proxy proxy) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder().connectionPool(new ConnectionPool(5, 1, TimeUnit.SECONDS));
        if (timeout == null) {
            builder.readTimeout(DEFAULT_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
        } else {
            builder.readTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS);
        }
        if (interceptor != null) {
            builder.addInterceptor(interceptor);
        }
        builder.proxy(proxy);
        return builder.build();
    }

    private static Retrofit defaultRetrofit(OkHttpClient client, ObjectMapper mapper) {
        return new Retrofit.Builder().baseUrl(BASE_URL).client(client).addConverterFactory(CustomJacksonConverterFactory.create(mapper)).addCallAdapterFactory(RxJava2CallAdapterFactory.create()).build();
    }

    private static Retrofit defaultRetrofit(OkHttpClient client, ObjectMapper mapper, String gateway) {
        if (gateway == null) {
            try {
                throw new Exception("网关地址不能为空");
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        Retrofit.Builder builder = new Retrofit.Builder().client(client).baseUrl(gateway).addConverterFactory(CustomJacksonConverterFactory.create(mapper)).addCallAdapterFactory(RxJava2CallAdapterFactory.create());
        return builder.build();
    }
}
